﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ares.Engine.Lib;
using Ionic.Zip;
using System.IO;
using Ares.Engine;
using System.Windows.Controls;

namespace Ares.Storage.Zip
{
    public class BackupZip : IBackUpStorage, IPlugin
    {
        private BackupProject CurrentProject;
        private BackupProtocol CurrentProtocol;
        private Dictionary<String, ZipFile> OpenZipFileHandles = new Dictionary<string, ZipFile>();
        private String CurrentFilename;
        private String CurrentDirectory;
        private long CurrentFileNo;
        private String Comment;
        private List<BackupProtocol> ProtocolsInFolder = new List<BackupProtocol>();

        /// <summary>
        /// Create a zip file adding all of the specified files.
        /// The files are added at the specified directory path in the zip file.
        /// </summary>
        /// <remarks>
        /// If the zip file exists then the file will be added to it.
        /// If the file already exists in the zip file an exception will be thrown.
        /// </remarks>
        /// <param name="filePaths">A collection of paths to files to be added to the zip.</param>
        /// <param name="zipFilePath">The fully-qualified path of the zip file to be created.</param>
        /// <param name="directoryPathInZip">The directory within the zip file where the file will be placed.
        /// Ex. specifying "files\\docs" will add the file(s) to the files\docs directory in the zip file.</param>
        /// <param name="deleteExisting">Delete the zip file if it already exists.</param>
        public void CreateZipFile(ICollection<FileInfo> filePaths, string zipFilePath, string directoryPathInZip, bool deleteExisting)
        {
            using (ZipFile zip = new ZipFile(zipFilePath))
            {
                foreach (FileInfo filePath in filePaths)
                {
                    zip.AddFile(filePath.FullName, directoryPathInZip);
                }
                zip.Save();
            }
        }  

        public void Save(BackupProtocol protocol, ref BackupProject project)
        {
            string filename = String.Empty;
            using (var containerzip = new ZipFile())
            {

                CurrentFileNo = 0;

                var files = from BackupEnt in protocol.BackupEntries where ((BackupEnt.State == BackupEntryState.Changed) || (BackupEnt.State == BackupEntryState.New)) select BackupEnt.Path;

                foreach (String file in files)
                {
                    CurrentFileNo++;

                }
            }
        }

        void zipfile_AddProgress(object sender, AddProgressEventArgs e)
        {
            if (e.BytesTransferred % 2048 == 0)
            host.DoReportAddProgress("[1/4] Adding files..", e.BytesTransferred, e.TotalBytesToTransfer, e.EntriesTotal, CurrentFileNo);
        }

        void zipfile_SaveProgress(object sender, SaveProgressEventArgs e)
        {
           
            if (e.BytesTransferred % 204800 == 0)
                host.DoReportSaveProgress("[2/4] Saving files..", e.BytesTransferred, e.TotalBytesToTransfer);
        }

        void containerzip_SaveProgress(object sender, SaveProgressEventArgs e)
        {
            if (e.BytesTransferred % 204800 == 0)
                host.DoReportSaveProgress("[4/4] Saving the container...", e.BytesTransferred, e.TotalBytesToTransfer);
        }

        void containerzip_AddProgress(object sender, AddProgressEventArgs e)
        {
            if (e.BytesTransferred % 204800 == 0)
                host.DoReportAddProgress("[3/4] Adding data and protocol...", e.BytesTransferred, e.TotalBytesToTransfer, e.EntriesTotal, CurrentFileNo);
        }

        public void RefreshProtocolsInFolder()
        {
            if (Directory.Exists(CurrentProject.Destination))
            {
                //Get all backup files
                string[] files = Directory.GetFiles(CurrentProject.Destination,
                                    "*.zip",
                                    SearchOption.TopDirectoryOnly);

                //A backupfile needs a valid comment
                foreach (string file in files)
                {
                    try
                    {
                        using (ZipFile zip = new ZipFile())
                        {      
                            zip.FullScan = true;
                            zip.Initialize(file);

                            BackupProtocol BP = new BackupProtocol();


                            BP = BackupProtocol.LoadBackupProjectFromXml(zip.Comment);
                            
                            BP.Filename = file;
                            ProtocolsInFolder.Add(BP);
                        }
                    }
                    catch
                    {
                        //So what...
                    }
                }
            }
        }

        public string Name
        {
            get
            {
                return "Zip";
            }
        }

        public string FileName
        {
            get
            {
                Type t = typeof(BackupZip);
                string s = t.Assembly.ManifestModule.ScopeName;
                return Path.GetFileNameWithoutExtension(s);
            }
        }

        public void InitPlugin()
        {
   
        }

        public IHost Host
        {
            get;
            set;
        }

        public BackupProtocol GetLastFullProtocol(BackupProject project)
        {
            RefreshProtocolsInFolder();
            //Get the last full backup
            BackupProtocol LastFullBackup = null;
            try
            {
                LastFullBackup =
                (from n in ProtocolsInFolder
                 where n.UsedMethod == BackupMethod.full
                 orderby n.Timestamp descending
                 select n).First();
            }
            catch
            {
            }
            return LastFullBackup;
        }

        private void ClearFolder(string FolderName)
        {
            DirectoryInfo dir = new DirectoryInfo(FolderName);

            foreach (FileInfo fi in dir.GetFiles())
            {
                fi.IsReadOnly = false;
                fi.Delete();
            }

            foreach (DirectoryInfo di in dir.GetDirectories())
            {
                ClearFolder(di.FullName);
                di.Delete();
            }
        }

        public void Revert(BackupProject project, uint IdTo)
        {
            switch (project.Method)
            {
                case BackupMethod.full:
                    BackupProtocol LastFullBackup = GetLastFullProtocol(project);
                    break;
                case BackupMethod.differential:
                    LastFullBackup = GetLastFullProtocol(project);
                    
                    //Clear the folder
                    ClearFolder(project.Source);

                    //Unzip last full backup
                    if (LastFullBackup != null)
                    {
                        using (ZipFile zip = ZipFile.Read(Path.Combine(project.Destination, LastFullBackup.Filename)))
                        {
                            ZipEntry e = zip["data.zip"];
                            using (MemoryStream stream = new MemoryStream())
                            {
                                e.Extract(stream);
                                stream.Seek(0, SeekOrigin.Begin);

                                using (ZipFile TempZip = ZipFile.Read(stream))
                                {
                                    foreach (ZipEntry entry in TempZip)
                                    {
                                        entry.Extract(project.Source, ExtractExistingFileAction.OverwriteSilently);
                                    }
                                }
                            }
                        }
                    }

                    //Get the diff
                    BackupProtocol protocol = (from p in ProtocolsInFolder where p.id == IdTo select p).First();
                    if (protocol != null && protocol.id != LastFullBackup.id)
                    {
                        //extract the entry
                        using (ZipFile TempZip = ZipFile.Read(Path.Combine(project.Destination, protocol.Filename)))
                        {
                            foreach (ZipEntry entry in TempZip)
                            {
                                entry.Extract(project.Source, ExtractExistingFileAction.OverwriteSilently);
                            }
                        }

                        //check for deletion
                        
                        //pfade nur relativ speichern
                    }
                    break;
                case BackupMethod.incremental:
                    BackupProtocol LastIncrementalBackup = GetLastIncrementalProtocol(project);
                    break;
                default:
                    break;
            }
            
        }

        public void Revert(BackupProject project, DateTime dt)
        {
           
        }

        public BackupProtocol GetLastIncrementalProtocol(BackupProject project)
        {
            return null;
        }

        public uint GetLastProtocolID()
        {
            RefreshProtocolsInFolder();
            uint highestid = (from p in ProtocolsInFolder orderby p.id descending select p).First().id;
            return highestid;
        }

        public DateTime GetLastProtocolDate()
        {
            RefreshProtocolsInFolder();
            DateTime date = (from p in ProtocolsInFolder orderby p.id descending select p).First().Timestamp;
            return date;
        }

        public List<BackupProtocol> GetAllProtocols()
        {
            RefreshProtocolsInFolder();
            return ProtocolsInFolder;
        }

        public void SetBackupProject(BackupProject project)
        {
            CurrentProject = project;
        }

        public IHost host
        {
            get;
            set;
        }


        public void SaveBytewise()
        {
           
        }


        public Stream GetStreamFromFile(ref BackupProject CurrentBackupProject, ref BackupProtocol LastFullBackupProtocol, string FilePath, string FileHash)
        {
            using (ZipFile zip = ZipFile.Read(LastFullBackupProtocol.Filename))
            {
                ZipEntry data = zip[FilePath];
                data.FileName += Guid.NewGuid().ToString();
                data.Extract(CurrentBackupProject.Source);
                Stream dataStream = new FileStream(Path.Combine(CurrentBackupProject.Source,data.FileName), 
                                                   FileMode.OpenOrCreate,
                                                   FileAccess.ReadWrite,
                                                   FileShare.Read,
                                                   4096,
                                                   FileOptions.DeleteOnClose);
                return dataStream;      
            }
        }

        public void EndSaveTransaction()
        {
            using (ZipFile Container = new ZipFile())
            {
                //Set some initial settings
                Container.AddProgress += new EventHandler<AddProgressEventArgs>(containerzip_AddProgress);
                Container.SaveProgress += new EventHandler<SaveProgressEventArgs>(containerzip_SaveProgress);
                                Container.UseZip64WhenSaving = Zip64Option.AsNecessary;
                Container.CompressionLevel = Ionic.Zlib.CompressionLevel.BestCompression;

                Container.AddDirectory(CurrentDirectory, String.Empty);

                //Add the protocol
                Container.Comment = Comment;

                Container.Save(CurrentFilename);
            }

            try
            {
                Array.ForEach(Directory.GetFiles(CurrentDirectory, "*.*", SearchOption.AllDirectories),
                                    delegate(string path) { File.Delete(path); });
                Directory.Delete(CurrentDirectory, true);
            }
            catch{}
        }

        public void InitSaveTransaction(ref BackupProject project, ref BackupProtocol protocol)
        {
            //Reset the project
            CurrentProject = project;
            CurrentProtocol = protocol;

            //Create Destination Directory
            if (!Directory.Exists(project.Destination))
            {
                Directory.CreateDirectory(project.Destination);
            }

            string tmp = Guid.NewGuid().ToString();

            //Create Temporary Directory
            Directory.CreateDirectory(Path.Combine(project.Destination,tmp));
            CurrentDirectory = Path.Combine(project.Destination, tmp);
            
            Comment = BackupProtocol.SaveBackupProjectToXML(protocol);

            DateTime DT = DateTime.UtcNow;
            CurrentFilename = String.Empty;
            switch (CurrentProtocol.UsedMethod)
            {
                case BackupMethod.full:
                    CurrentFilename = Path.Combine(CurrentProject.Destination, String.Format("[{0}] {1:yyyy-MM-dd--HH-mm-ss}--full.zip", CurrentProtocol.id, DT));
                    break;
                case BackupMethod.differential:
                    CurrentFilename = Path.Combine(CurrentProject.Destination, String.Format("[{0}] {1:yyyy-MM-dd--HH-mm-ss}--diff.zip", CurrentProtocol.id, DT));
                    break;
                case BackupMethod.incremental:
                    CurrentFilename = Path.Combine(CurrentProject.Destination, String.Format("[{0}] {1:yyyy-MM-dd--HH-mm-ss}--inc.zip", CurrentProtocol.id, DT));
                    break;
                case BackupMethod.differentialbyte:
                    CurrentFilename = Path.Combine(CurrentProject.Destination, String.Format("[{0}] {1:yyyy-MM-dd--HH-mm-ss}--diffbyte.zip", CurrentProtocol.id, DT));
                    break;
            }
        }

        /// <summary>
        /// Copies the contents of input to output. Doesn't close either stream.
        /// </summary>
        public void CopyStream(Stream input, Stream output)
        {
            byte[] buffer = new byte[8 * 1024];
            int len;
            while ((len = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, len);
            }
        }

        public void SaveEntry(BackupEntry Entry, FileInfo fileInfo, Stream dataStream)
        {
            //We need to save the files that are either new or have changed
            if (Entry.State == BackupEntryState.Changed || Entry.State == BackupEntryState.New)
            {
                //Add file via DataStream and with FileInfo
                //ZipEntry e = containerzip.AddEntry(fileInfo.FullName.Replace(CurrentProject.Source, String.Empty), dataStream);

                //Set the main infos
                //e.SetEntryTimes(fileInfo.CreationTime, fileInfo.LastAccessTime, fileInfo.LastWriteTime);
                //e.Attributes = fileInfo.Attributes;
                dataStream.Position = 0;
                string dirname = Path.GetDirectoryName(CurrentDirectory + fileInfo.FullName.Replace(CurrentProject.Source, String.Empty));
                if (!Directory.Exists(dirname))
                {
                    Directory.CreateDirectory(dirname);
                }
                using (FileStream fs = new FileStream(CurrentDirectory + fileInfo.FullName.Replace(CurrentProject.Source, String.Empty),
                                                            FileMode.OpenOrCreate,
                                                            FileAccess.ReadWrite))
                {
                    CopyStream(dataStream, fs);
                }
             }
        }

        public void InitRevertTransaction()
        {
            throw new NotImplementedException();
        }

        public void RevertEntry()
        {
            throw new NotImplementedException();
        }

        public void EndRevertTransaction()
        {
            throw new NotImplementedException();
        }


        public BackupProtocol GetProtocolByID(ref BackupProject CurrentBackupProject, uint ToID)
        {
            RefreshProtocolsInFolder();

            var protocol = from p in ProtocolsInFolder where p.id == ToID select p;

            if (protocol != null)
            {
                return (BackupProtocol)protocol.First();
            }

            return null;
        }

        public void RevertEntry(ref BackupProtocol protocol, BackupEntry entry, ref FileInfo fileInfo, ref Stream fileStream)
        {
            if (protocol != null)
            {
                uint protocolid = protocol.id;
                var file = from p in ProtocolsInFolder where p.id == protocolid select p.Filename;

                if (file != null)
                {
                    string filename = file.First();
                    
                    using (ZipFile zip = ZipFile.Read(filename))
                    {
                        ZipEntry e = zip[entry.Path];
                        e.Extract(fileStream);
                        fileStream.Close();
                        fileInfo.Attributes = e.Attributes;
                        fileInfo.CreationTime = e.CreationTime;
                        fileInfo.LastAccessTime = e.AccessedTime;
                        fileInfo.LastWriteTime = e.LastModified;
                    }
                }
            }
        }


        public void ReleaseStream(String SourcePath)
        {
            KeyValuePair<String, ZipFile> test = (from p in OpenZipFileHandles where p.Key == SourcePath select p).First();
            try
            {
                test.Value.Dispose();
                OpenZipFileHandles.Remove(test.Key);
            }
            catch { }
        }
    }
}
